#include "../include/lsusb.h"
#include "../include/io.h"
#include "../include/time.h"
#include "../include/pcihdr.h"
#include "../include/kasm.h"


static void reset_hc(uhci_t *s);
static void start_hc(uhci_t *s);
static void port_reset_hc(uhci_t *s);
static void uhci_wait_ms(int ms);
static void initFL(uhci_t *s);
static bool isConnected(uhci_t *s);

static bool usbDetect(uhci_t *s);
static char *getDevice(unsigned short vID, unsigned short dID);
static char *getVendor(unsigned short vID);
static char *getUHCIDevice(unsigned short vID, unsigned short dID);
static unsigned int getIO(PCI_Device_t *s);

/*Fuente: http://forum.osdev.org/viewtopic.php?f=1&t=17695*/
static void pci_readdevice(int bus, int dev, int func, PCI_Device_t *ret);
static int pci_read(int bus, int dev, int func, int reg);

/*Debug*/
static void uhci_show_sc (int port, unsigned short status);

int
lsusb(int argc, char **argv)
{
    uhci_t uhciDevice;

    if (!usbDetect(&uhciDevice))
    {
        printf("ERROR: No Host Controller detected\n");
        return -1;
    }

    initFL(&uhciDevice);
    reset_hc(&uhciDevice);
    start_hc(&uhciDevice);
    port_reset_hc(&uhciDevice);
    if (isConnected(&uhciDevice))
    {
        ; /*Nada por ahora*/
    }
//     debug(&uhciDevice);
//     frame(&uhciDevice);

    return 1;
}

static bool isConnected(uhci_t *s)
{
    unsigned int io_addr = s->io_addr;
    word port1, port2;

    port1 = inw (io_addr + USBPORTSC1);
    port2 = inw (io_addr + USBPORTSC2);

    if (port1 != 128 || port2 != 128)
    {
        printf("USB device plugged in\n");
        return TRUE;
    }

    printf("No USB device plugged in\n");
    return FALSE;

}

static void initFL(uhci_t *s)
{
    int i;

    for(i=0 ;i < FLLen ; i++)
        s->frameList[i] = 0;

    return;
}

static void start_hc (uhci_t *s)
{
    unsigned int io_addr = s->io_addr;
    int timeout = 1000;

    /*
        * Reset the HC - this will force us to get a
        * new notification of any already connected
        * ports due to the virtual disconnect that it
        * implies.
        */
    outw (USBCMD_HCRESET, io_addr + USBCMD);

    while (inw (io_addr + USBCMD) & USBCMD_HCRESET)
    {
        if (!--timeout)
        {
            printf("USBCMD_HCRESET timed out!");
            break;
        }
    }

    /* Turn on all interrupts */
    /*No las voy a usar*/
//     outw (USBINTR_TIMEOUT | USBINTR_RESUME |
//             USBINTR_IOC | USBINTR_SP, io_addr + USBINTR);

    /* Start at frame 0 */
    outw (0, io_addr + USBFRNUM);
//     outl (virt_to_bus (s->frameList), io_addr + USBFLBASEADD);

    /* Run and mark it configured with a 64-byte max packet */
//     outw (USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD);
    s->apm_state = 1;
    s->running = 1;
}


static void port_reset_hc(uhci_t *s)
{
    int status = 0;
    unsigned int io_addr = s->io_addr;

    /*Resetea*/
    SET_RH_PORTSTAT (USBPORTSC_PR);
    uhci_wait_ms(10);

    /*Limpia el estado de reseteo*/
    CLR_RH_PORTSTAT (USBPORTSC_PR);
    uhci_wait_ms(10);

    /*Habilita el puerto*/
    SET_RH_PORTSTAT (USBPORTSC_PE);

    uhci_wait_ms(10);
    SET_RH_PORTSTAT (0xa);
}

static void reset_hc(uhci_t *s)
{
    unsigned int io_addr = s->io_addr;

    s->apm_state = 0;
    /* Global reset for 50ms */
    outw (USBCMD_GRESET, io_addr + USBCMD);
    uhci_wait_ms (50);
    outw (0, io_addr + USBCMD);
    uhci_wait_ms (10);
}

void uhci_wait_ms(int ms)
{
    int sec = getSec();
    int sec2 = sec + 1;

    if ( sec2 == 60)
        sec2 = 0;

    do
    {
        sec = getSec();
    }while(sec < sec2);

}

bool
usbDetect(uhci_t *s)
{
    int bus, dev, func;
    char *deviceName;

    PCI_Device_t device;

    printf("USB Scan...\n");

    for (bus = 0; bus < 255; bus++)
    {
        for (dev = 0; dev < 32; dev++)
        {
            for (func = 0; func < 8; func++)
            {
                pci_readdevice(bus, dev, func, &device);
                if(device.VendorID != 0xFFFF)
                {
                    deviceName = getUHCIDevice(device.VendorID, device.DeviceID);
                    if (deviceName != NULL)
                    {
                        /*Encontró un usb*/

                        if ( (s->io_addr = getIO(&device)) != 0 )
                        {
                            printf("%s Found!\n", deviceName);
                            return TRUE;
                        }
                    }
                }
            }
        }
    }
    return FALSE;
}

static unsigned int
getIO(PCI_Device_t *s)
{
    unsigned int num;

    num = s->BAR0;
    if (num != 0 && num % 2 != 0)
        return s->BAR0 -1;

    num = s->BAR1;
    if (num != 0 && num % 2 != 0)
        return s->BAR1 -1;

    num = s->BAR2;
    if (num != 0 && num % 2 != 0)
        return s->BAR2 -1;

    num = s->BAR3;
    if (num != 0 && num % 2 != 0)
        return s->BAR3 -1;

    num = s->BAR4;
    if (num != 0 && num % 2 != 0)
        return s->BAR4 -1;

    num = s->BAR5;
    if (num != 0 && num % 2 != 0)
        return s->BAR5 -1;

    return 0;
}

char *
getUHCIDevice(unsigned short vID, unsigned short dID)
{
    int i = 0;

    while (i < uhciDevLen)
    {
        if (uhciDevices[i].vendorID == vID &&
            uhciDevices[i].deviceID == dID )
            return uhciDevices[i].device;
        i++;
    }

    return NULL;
}

/*Fuente: http://forum.osdev.org/viewtopic.php?f=1&t=17695*/
int
lspci(int argc, char **argv)
{
    int bus, dev, func, i;
    char *vendorID;
    char *deviceID;
    unsigned short lastVendor;
    unsigned short lastDevice;
    i=0;
    PCI_Device_t device;

    printf("PCI Scan...\n");

    for (bus = 0; bus < 255; bus++)
    {
        for (dev = 0; dev < 32; dev++)
        {
            lastVendor = -1;
            lastDevice = -1;

            for (func = 0; func < 8; func++)
            {
                pci_readdevice(bus, dev, func, &device);
                if(device.VendorID != 0xFFFF)
                {
                    if (device.VendorID != lastVendor && device.DeviceID != lastDevice)
                    {
                        lastVendor = device.VendorID;
                        lastDevice = device.DeviceID;

                        i++;
                        printf("\nbus %d|device %d|function %d\n", bus, dev, func);

                        if ( (vendorID = getVendor(device.VendorID)) != NULL )
                            printf("VendorID 0x%x => %s\n", device.VendorID, vendorID);
                        else
                            printf("VendorID 0x%x => Unknown\n", device.VendorID);

                        if ( (deviceID = getDevice(device.VendorID,
                                    device.DeviceID)) != NULL )
                            printf("DeviceID 0x%x => %s\n", device.DeviceID, deviceID);
                        else
                            printf("DeviceID 0x%x => Unknown\n", device.DeviceID);
                    }
                }
            }
        }
    }
   printf("\nScan complete! %d devices found!\n", i);
   return 1;
    
}

char *
getVendor(unsigned short vID)
{
    int i = 0;

    while (i < pciVenLen)
    {
        if (pciVendors[i].vendorID == vID)
            return pciVendors[i].vendor;
        i++;
    }

    return NULL;
}

char *
getDevice(unsigned short vID, unsigned short dID)
{
    int i = 0;

    while (i < pciDevLen)
    {
        if (pciDevices[i].vendorID == vID &&
            pciDevices[i].deviceID == dID )
            return pciDevices[i].device;
        i++;
    }

    return NULL;
}

/*Fuente: http://forum.osdev.org/viewtopic.php?f=1&t=17695*/
void
pci_readdevice(int bus, int dev, int func, PCI_Device_t *ret)
{
   ret->VendorID      =   (short) (pci_read(bus,dev,func,0)&0x0000FFFF);
   ret->DeviceID      =   (short)((pci_read(bus,dev,func,0)&0xFFFF0000) >> 16);
   ret->CommandReg    =   (short) (pci_read(bus,dev,func,4)&0x0000FFFF);
   ret->StatusReg     =   (short)((pci_read(bus,dev,func,4)&0xFFFF0000) >> 16);
   ret->RevisionID    =   (short) (pci_read(bus,dev,func,8)&0x0000FFFF);
   ret->SubClass      =   (char) ((pci_read(bus,dev,func,8)&0x00FF0000) >> 16);
   ret->ClassCode     =   (char) ((pci_read(bus,dev,func,8)&0xFF000000) >> 24);
   ret->CachelineSize =   (char)  (pci_read(bus,dev,func,12)&0x000000FF);
   ret->Latency       =   (char) ((pci_read(bus,dev,func,12)&0x0000FF00) >> 8);
   ret->HeaderType    =   (char) ((pci_read(bus,dev,func,12)&0x00FF0000) >> 16);
   ret->BIST          =   (char) ((pci_read(bus,dev,func,12)&0xFF000000) >> 24);
   ret->BAR0          =    pci_read(bus, dev, func, 16);
   ret->BAR1          =    pci_read(bus, dev, func, 20);
   ret->BAR2          =    pci_read(bus, dev, func, 24);
   ret->BAR3          =    pci_read(bus, dev, func, 28);
   ret->BAR4          =    pci_read(bus, dev, func, 32);
   ret->BAR5          =    pci_read(bus, dev, func, 36);
   ret->CardbusCISPtr =    pci_read(bus, dev, func, 40);
   ret->SubVendorID   =   (short) (pci_read(bus,dev,func,44)&0x0000FFFF);
   ret->SubDeviceID   =   (short)((pci_read(bus,dev,func,44)&0xFFFF0000) >> 16);
   ret->ExRomAddress  =    pci_read(bus, dev, func, 48);
   ret->Reserved1     =    pci_read(bus, dev, func, 52);
   ret->Reserved2     =    pci_read(bus, dev, func, 56);
   ret->IRQ           =   (char)  (pci_read(bus,dev,func,60)&0x000000FF);
   ret->PIN           =   (char) ((pci_read(bus,dev,func,60)&0x0000FF00) >> 8);
   ret->MinGrant      =   (char) ((pci_read(bus,dev,func,60)&0x00FF0000) >> 16);
   ret->MaxLatency    =   (char) ((pci_read(bus,dev,func,60)&0xFF000000) >> 24);
   return;
}

static void uhci_show_sc (int port, unsigned short status)
{
    printf("  stat%d     =     %04x   %d%d%d%d%d%d%d%d",
        port,
        status,
        (status & USBPORTSC_SUSP) ? "PortSuspend " : "",
         (status & USBPORTSC_PR) ? "PortReset " : "",
          (status & USBPORTSC_LSDA) ? "LowSpeed " : "",
           (status & USBPORTSC_RD) ? "ResumeDetect " : "",
            (status & USBPORTSC_PEC) ? "EnableChange " : "",
             (status & USBPORTSC_PE) ? "PortEnabled " : "",
              (status & USBPORTSC_CSC) ? "ConnectChange " : "",
               (status & USBPORTSC_CCS) ? "PortConnected " : "");
}



/*Fuente: http://forum.osdev.org/viewtopic.php?f=1&t=17695*/
int
pci_read(int bus, int dev, int func, int reg)
{
  outportl(0xCF8, ((unsigned long) 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | reg));
  return inportl(0xCFC);
}
